#include "DotModel.h"
#include "../Utils/SequentialRandom.h"

DotModel::DotModel(bool bInitField, QObject *parent) :
    QObject(parent),mnDotsHorzontal(5),mnDotsVertical(5),
    mnRows(mnDotsVertical - 1),mnColumns(mnDotsHorzontal - 1),mnLineLenHorizontal(50),mnLineLenVertical(50)
{
    clearField();

    if(bInitField){
        initField();
    }
}
DotModel::~DotModel()
{
    clearField();
}

void DotModel::initField()
{
    clearField();

    mDots.setRows(mnDotsVertical);
    mDots.setCols(mnDotsHorzontal);

    mCells.setRows(mnRows);
    mCells.setCols(mnColumns);    

    mnTotalLines = 4 + (3 * (mnRows + mnColumns - 2)) + (2 * (mnRows - 1) * (mnColumns - 1));

    for(unsigned int nRow = 1; nRow <= mnDotsVertical; nRow++){
        for(unsigned int nCol = 1; nCol <= mnDotsHorzontal; nCol++){
            int nEdge = 2;

            nEdge += (nRow == 1 || nRow == mnDotsVertical ? 0 : 1);
            nEdge += (nCol == 1 || nCol == mnDotsHorzontal ? 0 : 1);
            mDots.set(nRow,nCol,new Dot(mptStart + QPoint((nCol - 1) * mnLineLenHorizontal,(nRow - 1) *mnLineLenVertical),nEdge,nRow,nCol));
        }
    }

    for(unsigned int nRow = 1; nRow <= mnRows; nRow++){
        for(unsigned int nCol = 1; nCol <= mnColumns; nCol++){
            mCells.set(nRow,nCol,new Cell(mptStart + QPoint((nCol - 1) * mnLineLenHorizontal,(nRow - 1) *mnLineLenVertical) ,QSize(mnLineLenHorizontal + 1,mnLineLenVertical + 1),nRow,nCol));
        }
    }

    mRectField = QRect(mptStart,QSize(mnColumns * mnLineLenHorizontal + 1, mnRows * mnLineLenVertical + 1));
    emit needToResized(mnColumns * mnLineLenHorizontal, mnRows * mnLineLenVertical);
}

void DotModel::clearField()
{
    mDots.clear();
    mCells.clear();

    for(int i = 0; i < mLines.size(); i++){
        delete mLines[i];
    }

    mLines.clear();
    mnTotalLines = 0;
    mnPlayer1Cells = 0;
    mnPlayer2Cells = 0;
}

void DotModel::reset()
{
    clearField();
    mnRows = mnColumns = mnLineLenHorizontal = mnLineLenVertical = mnDotsHorzontal = mnDotsVertical = 0;
}

void DotModel::setDotsHorz_Vert(unsigned int nDotsHorz, unsigned int nDotsVert,bool bInitField)
{
    if(nDotsHorz < 2){
        nDotsHorz = 2;
    }

    if(nDotsVert < 2){
        nDotsVert = 2;
    }

    mnDotsHorzontal = nDotsHorz;
    mnDotsVertical = nDotsVert;
    mnRows = mnDotsVertical - 1;
    mnColumns = mnDotsHorzontal - 1;

    if(bInitField){
        initField();
    }
}

unsigned int DotModel::dotsHorizontal(){return mnDotsHorzontal;}

unsigned int DotModel::dotsVertical(){return mnDotsVertical;}

void DotModel::setLineLengthHorz_Vert(unsigned int nLineLenHorz, unsigned int nLineLenVert,bool bInitField)
{    
    mnLineLenHorizontal = nLineLenHorz;
    mnLineLenVertical = nLineLenVert;

    if(bInitField){
        initField();
    }
}

unsigned int DotModel::lineLengthHorz(){return mnLineLenHorizontal;}

unsigned int DotModel::lineLengthVert(){return mnLineLenVertical;}

void DotModel::setStartPoint(QPoint ptStart,bool bInitField)
{    
    mptStart = ptStart;

    if(bInitField){
        initField();
    }
}

QPoint DotModel::startPoint(){return mptStart;}

QList<Dot*> DotModel::dotsNearToPoint(QPoint pos)
{
    return mDots.find(pos);
}


int DotModel::addLine(Line *pLine)
{
    if(pLine){
        bool bAdded = false;

        QList<Cell*> cells1 = mCells.find(pLine->dot1()->point());
        QList<Cell*> cells2 = mCells.find(pLine->dot2()->point());
        bool bCellCompleted = false;

        for(int i = 0; i < cells1.count(); i++){
            for(int j = 0; j < cells2.count(); j++){
                if(cells1[i] == cells2[j]){
                    bAdded = cells1[i]->addLine(pLine) || bAdded;

                    if(bAdded && cells1[i]->isCompleted()){
                        if(pLine->owner() == Game::Player1){
                            mnPlayer1Cells++;
                        }
                        else if(pLine->owner() == Game::Player2){
                            mnPlayer2Cells++;
                        }
                    }

                    bCellCompleted = bCellCompleted || cells1[i]->isCompleted();
                }
            }
        }

        if(bAdded){
            mLines.append(pLine);
            pLine->dot1()->increase();
            pLine->dot2()->increase();

            if(mLines.count() == int(mnTotalLines)){
                return 2; // Game finished
            }

            if(bCellCompleted){
                return 1; //cell completed so turn will keep to same player
            }
        }
        else{
            return -1;
        }
    }

    return 0; // Turn will change
}

int DotModel::removeLine(Line *pLine)
{
    if(pLine){
        for(int i = 0; i < mLines.count(); i++ ){
            if(mLines.at(i)->isSame(pLine->line())){
                pLine = mLines.at(i);
                mLines.removeOne(pLine);
                //break;
            }
        }

        pLine->dot1()->decrease();
        pLine->dot2()->decrease();

        QList<Cell*> cells1 = mCells.find(pLine->dot1()->point());
        QList<Cell*> cells2 = mCells.find(pLine->dot2()->point());

        for(int i = 0; i < cells1.count(); i++){
            for(int j = 0; j < cells2.count(); j++){
                if(cells1[i] == cells2[j]){
                    bool bCompleted = cells1[i]->isCompleted();
                    Game::Player owner = cells1[i]->owner();
                    bool bErased = cells1[i]->removeLine(pLine);

                    if(bErased && bCompleted){
                        if(owner == Game::Player1){
                            mnPlayer1Cells--;
                        }
                        else if(owner == Game::Player2){
                            mnPlayer2Cells--;
                        }
                    }
                }
            }
        }
    }

    return 0; // Turn will change
}

OneD2TwoD<Cell*> *DotModel::cells(){return &mCells;}

QList<Line*> *DotModel::lines(){return &mLines;}

OneD2TwoD<Dot*> *DotModel::dots(){return &mDots;}

void DotModel::caclWinner()
{
    mnPlayer1Cells = 0;
    mnPlayer2Cells = 0;
    unsigned int nTotalCells = mCells.capacity();

    for(unsigned int i = 1; i <= nTotalCells; i++){
        Cell *pCell = mCells.get(i);

        if(pCell && pCell->isCompleted()){
            if(pCell->owner() == Game::Player1){
                mnPlayer1Cells++;
            }
            else if(pCell->owner() == Game::Player2){
                mnPlayer2Cells++;
            }
            else{
                qDebug("DotModel::caclWinner --> Cell Winner not found");
            }
        }
        else{
            qDebug("DotModel::caclWinner --> Cell Not Completed");
        }
    }
}

unsigned int DotModel::playerCellsCount(Game::Player player)
{
    if(player == Game::Player1){
        return mnPlayer1Cells;
    }
    else if(player == Game::Player2){
        return mnPlayer2Cells;
    }

    return 0;
}



QRect DotModel::fieldRect(){return mRectField;}

bool DotModel::isLineAdded(QLine line)
{
    for(int i = 0; i < mLines.count(); i++){
        if(mLines.at(i)->isSame(line)){
            return true;
        }
    }

    return false;
}

QList<Dot*> DotModel::dotsEligibleFrom(Dot *dot)
{
    QList<Dot*> dots;

    Dot *d = mDots.left(dot);

    if(d && d->isSelectable() && !isLineAdded(QLine(dot->point(),d->point()))){
        dots.append(d);
    }

    d = mDots.top(dot);

    if(d && d->isSelectable() && !isLineAdded(QLine(dot->point(),d->point()))){
        dots.append(d);
    }

    d = mDots.right(dot);

    if(d && d->isSelectable() && !isLineAdded(QLine(dot->point(),d->point()))){
        dots.append(d);
    }

    d = mDots.bottom(dot);

    if(d && d->isSelectable() && !isLineAdded(QLine(dot->point(),d->point()))){
        dots.append(d);
    }

    return dots;
}

uint DotModel::totalCells(){return mCells.count();}

uint DotModel::totalDots(){return mDots.count();}


QList<Cell*> DotModel::cellsWinableNow()
{
    QList<Cell*> cellsToWinNow;
    unsigned int nTotalCells = mCells.capacity();

    for(unsigned int i = 1; i <= nTotalCells; i++){
        Cell *pCell = mCells.get(i);

        if(pCell->isWinableNow()){
            cellsToWinNow.append(pCell);
        }
    }

    return cellsToWinNow;
}

QList<Cell*> DotModel::cellsWinableLater()
{
    QList<Cell*> cellsToWinLater;
    unsigned int nTotalCells = mCells.capacity();

    for(unsigned int i = 1; i <= nTotalCells; i++){
        Cell *pCell = mCells.get(i);

        if(pCell->lineAddedCount() < 3){
            cellsToWinLater.append(pCell);
        }
    }

    return cellsToWinLater;
}

QLine DotModel::checkLeftCell(Cell *pCell,int nAdjacentCellLineAddedCount, Cell **pNextCell)
{
    QLine line;

    if(!pCell->isLineAdded(pCell->leftLine())){
        Cell *pTmpCell = mCells.left(pCell);

        if(!pTmpCell || (pTmpCell->lineAddedCount() < nAdjacentCellLineAddedCount)){
            line = pCell->leftLine();
        }

        *pNextCell = pTmpCell;
    }

    return line;
}

QLine DotModel::checkTopCell(Cell *pCell,int nAdjacentCellLineAddedCount, Cell **pNextCell)
{
    QLine line;

    if(!pCell->isLineAdded(pCell->topLine())){
        Cell *pTmpCell = mCells.top(pCell);

        if(!pTmpCell || (pTmpCell->lineAddedCount() < nAdjacentCellLineAddedCount)){
            line = pCell->topLine();
        }

        *pNextCell = pTmpCell;
    }

    return line;
}

QLine DotModel::checkRightCell(Cell *pCell,int nAdjacentCellLineAddedCount, Cell **pNextCell)
{
    QLine line;

    if(!pCell->isLineAdded(pCell->rightLine())){
        Cell *pTmpCell = mCells.right(pCell);

        if(!pTmpCell || (pTmpCell->lineAddedCount() < nAdjacentCellLineAddedCount)){
            line = pCell->rightLine();
        }

        *pNextCell = pTmpCell;
    }

    return line;
}

QLine DotModel::checkBottomCell(Cell *pCell, int nAdjacentCellLineAddedCount, Cell **pNextCell)
{
    QLine line;

    if(!pCell->isLineAdded(pCell->bottomLine())){
        Cell *pTmpCell = mCells.bottom(pCell);

        if(!pTmpCell || (pTmpCell->lineAddedCount() < nAdjacentCellLineAddedCount)){
            line = pCell->bottomLine();
        }

        *pNextCell = pTmpCell;
    }

    return line;
}

QList<QLine> DotModel::getSidesThatAreNotAddedDependingOnAdjacentCells(int nAdjacentCellLineAddedCount,Cell *pCell)
{
    QList<QLine> lines;

    FunctorLine<DotModel> fl(this);
    fl.appendFunction(&DotModel::checkLeftCell);
    fl.appendFunction(&DotModel::checkTopCell);
    fl.appendFunction(&DotModel::checkRightCell);
    fl.appendFunction(&DotModel::checkBottomCell);

    SequentialRandom sRnd(3,0);
    Cell *ppCell;

    for(int i = 0; i < 4; i++){
        QLine line = fl(pCell,nAdjacentCellLineAddedCount,&ppCell,sRnd.next());

        if(!line.isNull()){
            if(!lines.contains(line)){
                lines.append(line);
            }
        }
    }

    return lines;
}

uint DotModel::totalLines(){return mnTotalLines;}

QLine DotModel::getWinLineAndNextCell(Cell *pCell,Cell **pNextCell)
{
    QLine line;

    if(pCell && pCell->isWinableNow()){
        line = checkLeftCell(pCell,4,pNextCell);

        if(line.isNull()){
            line = checkTopCell(pCell,4,pNextCell);
        }

        if(line.isNull()){
            line = checkRightCell(pCell,4,pNextCell);
        }

        if(line.isNull()){
            line = checkBottomCell(pCell,4,pNextCell);
        }
    }

    return line;
}

